package tum0r.webengine.models.core

import tum0r.webengine.config.ApplicationConfig
import tum0r.webengine.config.BaseConfig
import tum0r.webengine.config.SessionConfig
import tum0r.webengine.enums.EConfigType
import tum0r.webengine.extension.*
import java.io.File
import java.io.InputStream
import java.net.Socket
import java.util.*

/**
 * 说明: 请求信息
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/26 17:07
 *
 * @param[socket] 请求Socket
 * @param[headers] 请求头
 * @param[body] 请求内容
 */
data class HttpRequest(private val socket: Socket, val headers: LinkedHashMap<String, String>, val body: LinkedHashMap<String, Any?>) {
    /**
     * @param[socket] 请求Socket
     */
    constructor(socket: Socket) : this(socket, LinkedHashMap<String, String>(), LinkedHashMap<String, Any?>())

    /**
     * 请求关联的Session信息
     */
    @JvmField
    var session: Session? = null

    /**
     * 换行符<cr>的byte类型数字
     */
    private val rSep = '\r'.code.toByte()

    /**
     * 换行符<cn>的byte类型数字
     */
    private val nSep = '\n'.code.toByte()

    /**
     * multipart/form-data提交表单时参数名匹配正则
     */
    private val nameRegex = Regex("name=\"(.*?)\"")

    /**
     * multipart/form-data提交文件时文件名匹配正则
     */
    private val filenameRegex = Regex("filename=\"(.*?)\"")

    /**
     * 添加请求内容
     */
    private fun addBody(name: String, value: Any) {
        if (name.isNotBlank()) body[name] = value
    }

    /**
     * 解析&分隔的请求参数
     */
    private fun parseParameter(parameterString: String?) {
        if (parameterString != null && parameterString.isNotBlank()) {
            val parameters = parameterString.split("&")
            for (param in parameters) {
                val kv = param.split("=")
                addBody(kv[0].urlDecode(), if (kv.size == 2) kv[1].urlDecode() else "")
            }
        }
    }

    /**
     * 解析multipart/form-data格式提交的表单
     */
    private fun parseBoundary(inputStream: InputStream, boundary: String) {
        var contentDisposition = String(inputStream.readLine()).trim()
        if (contentDisposition.contains(boundary)) contentDisposition = String(inputStream.readLine()).trim()
        val contentType = if (filenameRegex.containsMatchIn(contentDisposition)) String(inputStream.readLine()).trim() else ""
        inputStream.readLine()
        val name = if (nameRegex.containsMatchIn(contentDisposition)) nameRegex.find(contentDisposition)!!.groupValues[1].urlDecode() else ""
        if (contentType != "") {
            ApplicationConfig.UPLOADS_DIR.createDir()
            val filename = filenameRegex.find(contentDisposition)!!.groupValues[1]
            val file = File("${ApplicationConfig.UPLOADS_DIR.absolutePath}${File.separator}${UUID.randomUUID().toString().replace("-", "")}.${filename.fileExtension()}")
            file.createNewFile()
            while (true) {
                val data = inputStream.readLine()
                if (String(data).contains(boundary)) break
                file.appendBytes(data)
            }
            addBody(name, EngineFile(file, filename, contentType.split(":")[1].trim()))
        } else {
            val data = String(inputStream.readLine())
            if (!data.contains(boundary)) addBody(name, data.trim().urlDecode())
        }
    }

    /**
     * 解析请求信息，包括请求头和请求内容
     */
    fun parse() {
        val inputStream = socket.getInputStream()
        val requestBytes = ArrayList<Byte>()
        // region 解析请求头
        var data: Int
        var flag = 0
        // 读取请求信息
        while (inputStream.read().also { data = it } != -1) {
            val dataByte = data.toByte()
            requestBytes.add(dataByte)
            // 如果出现\r\n\r\n则以读完所有请求头信息
            // HTTP协议中规定每行使用\r\n分隔，并且如果一行只包含\r\n代表请求头结束
            if (dataByte == rSep || dataByte == nSep) {
                flag++
                if (flag == 4) break
            } else
                flag = 0
        }
        val headersArray = String(requestBytes.toByteArray()).trim().split("\r\n")
        // 请求头信息首行包含请求方式、请求地址和请求版本，以空格分隔
        val firstLine = headersArray[0].split(" ")
        headers["RequestMethod"] = firstLine[0].uppercase(Locale.getDefault())
        val url = firstLine[1].split("?")
        if (headers["RequestMethod"].equals("GET")) {
            if (url.size == 2) parseParameter(url[1])
        } else if (!headers["RequestMethod"].equals("POST") && !headers["RequestMethod"].equals("HEAD"))
            "不支持的请求方式，请使用GET或POST请求".check(true, 405)
        headers["RequestURL"] = ApplicationConfig.regexRequestURL.replace(url[0], "/").urlDecode()
        headers["RequestVersion"] = firstLine[2]
        "不受支持的HTTP(s)版本或没有使用HTTP(s)协议请求".check(!headers["RequestVersion"]?.contains("HTTP", true)!!, 505)

        if (BaseConfig.isState(EConfigType.RETURN_SESSION)) {
            val sessionNameRegex = Regex("${SessionConfig.SESSION_NAME}=([a-zA-Z0-9]*);?")
            val cookie = headers.getOrDefault("Cookie", "")
            if (sessionNameRegex.containsMatchIn(cookie)) {
                val sessionID = sessionNameRegex.find(cookie)!!.groupValues[1]
                if (SessionConfig.SERVER_SESSION.containsKey(sessionID)) this.session = SessionConfig.SERVER_SESSION[sessionID]
                if (this.session != null && this.session!!.isTimeOut()) {
                    this.session = null
                    SessionConfig.SERVER_SESSION.remove(sessionID)
                }
                "Session已过期".check(this.session == null)
            } else {
                this.session = Session()
                SessionConfig.SERVER_SESSION[this.session!!.ID] = this.session!!
            }
        }

        // 其他请求头信息
        for (i in 1 until headersArray.size) {
            val temp = headersArray[i]
            val index = temp.indexOf(':')
            if (index != -1) headers[temp.substring(0, index)] = if (index < temp.length - 1) temp.substring(index + 1).trim() else ""
        }
        // endregion

        // region 解析POST的内容
        if (headers["RequestMethod"].equals("POST")) {
            val contentType = headers.getOrDefault("Content-Type", "")
            if (!contentType.contains("multipart/form-data")) {
                var length = headers["Content-Length"]?.toInt() ?: 0
                requestBytes.clear()
                while (length-- > 0) {
                    val dataByte = inputStream.read().toByte()
                    requestBytes.add(dataByte)
                }
                parseParameter(String(requestBytes.toByteArray()))
            } else {
                inputStream.readLine()
                val boundary = contentType.split("boundary=")[1]
                while (inputStream.available() != 0) parseBoundary(inputStream, boundary)
            }
        }
    }
}